package tools.gis;

import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.measure.Measure;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;

import datatypes.Antenna;
import datatypes.TerrainType;


/**
 * GIS tools.  Groups all useful methodes concerning GIS together.
 * 
 * @author Bart Moens
 */
public class GisTools {
	

	/**
	 * Uses GIS information to check out the terrain type
	 * 
	 * TODO check
	 * 
	 * @param a First antenna
	 * @param b Second antenna
	 * @return org.geotools.measure.Measure: distance in meters
	 */
	public static final TerrainType getTerrainType(Antenna a) {
		return a.getTerrainType();
	}
	
	
	/**
	 * Merges a list of gemetry into one.  Very naive implementation!!
	 * 
	 * TODO Multithreaded, divide-and-conquer strategy 
	 * 
	 * @param geometryCollection
	 * @return merged gemetry
	 */
	public static final Geometry combineIntoOneGeometry(Geometry[] geometryCollection ){
    	Geometry all = null;
    	for( Geometry geometry : geometryCollection ){
    		if( geometry == null ) continue;
    		if( all == null ){
    			all = geometry;
    		}
    		else {
    			all = all.union( geometry );
    		}
    	}
    	return all;
	}
	
	/**
	 * Merges a list of gemetry into one.  Very naive implementation!!
	 * 
	 * TODO Multithreaded, divide-and-conquer strategy 
	 * 
	 * @param geometryCollection
	 * @return merged gemetry
	 */
	public static final Geometry mergeGeometry(Geometry a, Geometry b){
    	if(a == null && b == null) 	return null;
    	else if (a == null) 		return b;
    	else if (b == null) 		return a;
    	else 						return a.union(b);
	}
	
	/**
	 * Geometry Conversion between WGS84 format and Lambert Belgium format<br>
	 * <br>
	 * EPSG:31370  Belge 1972 / Belge Lambert 72<br>
	 * <br>
	 * EPSG:4326   WGS84 geocentrique spatialreference<br>
	 * <br>
	 * @param g Gemetry to Convert
	 * @return Gemetry in new projection
	 * @throws TransformException Exception if transform doens't find all data.  
	 * <br><b>Make sure there is only one EPSG jar imported!</b><br>
	 */
	public static Geometry convertWGSToLambert(Geometry g) throws TransformException {
		try {
			CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326");  
			CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:31370"); 
			MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS);
			return JTS.transform( g, transform);
		} catch (Exception e) {
			System.err.println("Transform Exception!");
			e.printStackTrace();
		}
		throw new TransformException();
	}
		
	
	/**
	 * Geometry Conversion between WGS84 format and Lambert Belgium format<br>
	 * <br>
	 * EPSG:31370  Belge 1972 / Belge Lambert 72<br>
	 * <br>
	 * EPSG:4326   WGS84 geocentrique spatialreference<br>
	 * <br>
	 * @param g Gemetry to Convert
	 * @return Gemetry in new projection
	 * @throws TransformException Exception if transform doens't find all data.  
	 * <br><b>Make sure there is only one EPSG jar imported!</b><br>
	 */
	public static Geometry convertLambertToWSG(Geometry g) throws TransformException {
		try {
			CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:31370");  
			CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4326"); 
			MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS);
			return JTS.transform( g, transform);
		} catch (Exception e) {
			System.err.println("Transform Exception!");
			e.printStackTrace();
		}
		throw new TransformException();
	}
	

	/**
	 * Creates a geometry close to a circle in the Lambert coordinate system
	 * 
	 * @param longitude
	 * @param latitude
	 * @param radius in meters
	 * @param sides level of detail in circle
	 * 
	 * @return Geometry
	 */
	public static Geometry createCircleInLambertFormat( double latitude,double longitude,  double RADIUS, int SIDES) {
	    Coordinate coords[] = new Coordinate[SIDES+1];
	    for( int i = 0; i < SIDES; i++){
	        double angle = ((double) i / (double) SIDES) * Math.PI * 2.0;
	        double dx = Math.cos( angle ) * RADIUS;
	        double dy = Math.sin( angle ) * RADIUS;
	        coords[i] = new Coordinate( (double) latitude + dx, (double) longitude + dy );
	    }
	    coords[SIDES] = (Coordinate) coords[0].clone();
	    GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory( null );

	    	LinearRing ring = geometryFactory.createLinearRing( coords );
		    Polygon polygon = geometryFactory.createPolygon( ring, null );
		    
		    return polygon;	
	}
	
	
	
	
	
	
	/**
	 * Test klasse voor demonstreren fouten in conversie van formaten en distance-calcs.
	 * 
	 * @param args
	 */
	public static void main(String[] args){    
		try {
			GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory( null );
			
			// We weten dat lambert en wgs equivalent zijn!
			Geometry lambert = geometryFactory.createPoint(new Coordinate(150062.4,170116.1)); 
			Geometry wgs84   = geometryFactory.createPoint(new Coordinate(50.841322,4.369474));
			
			//Antenne: L_RNF17QAN_GSM09
			//X: 150062,4
			//Y: 170116,1
			//Lat: 50,841322
			//Long: 4,369474	
						
			
			//conversion tests
			System.out.println("Conversion test case from L_RNF17QAN_GSM09");
			System.out.println("Antenne: L_RNF17QAN_GSM09\nX: 150062,4\nY: 170116,1\nLat: 50,841322\nLong: 4,369474\n");
			System.out.println("Lambert->WSG: " + convertLambertToWSG(lambert));
			System.out.println("    instead of      (50,841322          4,369474         )");
			System.out.println("wgs84->Lambert:" + convertWGSToLambert(wgs84));
			System.out.println("    instead of       (150062,4          170116,1          )");
			System.out.println("\n");
			
			
			//distance tests
			System.out.println("Distance test case between K742266_D_1800_0_6 &  L_RNF17QAN_GSM09");
			System.out.println("Between     (50.841322,4.369474) and (50.848858,4.365199):");
			System.out.println("or lambert: (150062.5 ,170935.3) and (149761.5,170935.3)");
			
			System.out.println("\tGoogle total distance: 885.2 m");
			System.out.println("\tSome site: 890.774 m");
			
			
			Measure c = DefaultGeographicCRS.WGS84.distance(
					new double[]{50.841322,4.369474}, 
					new double[]{50.848858,4.365199});

			System.out.println("\tDistance using WGS84: " + c);

			Geometry lambert1 = geometryFactory.createPoint(new Coordinate(150062.4,170116.1)); 
			Geometry lambert2 = geometryFactory.createPoint(new Coordinate(149761.5,170935.3)); 
			System.out.println("\tDistance using Lambert: " + lambert1.distance(lambert2));
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
